﻿using Iworld.Author.Manager;
using Iworld.Author.Model;
using Iworld.Basis.Help;
using Iworld.Basis.Manager;
using Iworld.Game.Model;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Iworld.Game.Manager
{
    /// <summary>
    /// 投注管理器
    /// </summary>
    [RegisterToManagerService]
    public class BettingManager : ManagerBase<IModelToDbContextOfGame, BettingManager.Actions, Betting>
    {
        public BettingManager(IModelToDbContextOfGame db)
            : base(db)
        {

        }
        #region 缓存投注信息相关
        
      
        /// <summary>
        /// 当前局缓存读写锁
        /// </summary>
       private static ReaderWriterLock tempLock = new ReaderWriterLock();

        /// <summary>
        /// 读写超时时间(毫秒)
        /// </summary>
       private static int timeOut = 10000;

        /// <summary>
        /// 当前局投注的缓存
        /// </summary>
       private static Dictionary<int, TempBetting> currentRoundBettingTemps = new Dictionary<int, TempBetting>();

       /// <summary>
       /// 当前局总的投注量(黑桃,红桃,梅花,方块,王)
       /// </summary>
       private static double currentAll = 0;

       private static double currentSumSpade = 0;
       private static double currentSumHeart = 0;
       private static double currentSumClub = 0;
       private static double currentSumDiamond = 0;
       private static double currentSumKing = 0;

       /// <summary>
       /// 清除本局的缓存
       /// </summary>
       static void ClearCurrentTemp()
       {
           try
           {
               tempLock.AcquireWriterLock(timeOut);
               currentRoundBettingTemps.Clear();
               currentAll = 0;
               currentSumSpade = 0;
               currentSumHeart = 0;
               currentSumClub = 0;
               currentSumDiamond = 0;
               currentSumKing = 0;
           }
           finally
           {
               tempLock.ReleaseWriterLock();
           }
        
       }

       static double GetIncrement(double d)
       {
           if (d < 0) return 0;
           return d;
       }
      /// <summary>
       /// 更新投注缓存
      /// </summary>
      /// <param name="betting"></param>
      /// <param name="color"></param>
      /// <returns></returns>
       TempBetting CacheOrRefreshBetting(TempBetting betting, PokerColor color)
       {
           try
           {
               tempLock.AcquireWriterLock(timeOut);
               TempBetting exist = currentRoundBettingTemps.ContainsKey(betting.UserId) ? currentRoundBettingTemps[betting.UserId] : null;
               ///增量
               double incrementSpade = 0;
               double incrementHeart = 0;
               double incrementClub = 0;
               double incrementDiamond = 0;
               double incrementKing = 0;
               if (exist == null)
               {
                   incrementSpade = betting.Spade;
                   incrementHeart = betting.Heart;
                   incrementClub = betting.Club;
                   incrementDiamond = betting.Diamond;
                   incrementKing = betting.King;
                   currentRoundBettingTemps[betting.UserId] = exist = new TempBetting(betting.GameRoundNum,betting.UserId);
               }
               else
               {
                   incrementSpade = GetIncrement(betting.Spade - exist.Spade);
                   incrementHeart = GetIncrement(betting.Heart - exist.Heart);
                   incrementClub = GetIncrement(betting.Club - exist.Club);
                   incrementDiamond = GetIncrement(betting.Diamond - exist.Diamond);
                   incrementKing = GetIncrement(betting.King - exist.King);
               }
               exist.Username = betting.Username;
               exist.Golds = betting.Golds;
               #region 限红处理
               double limit = GameManager.GetCurrentLimitRed();
               if (limit > 0)
               {
                   ///当前中奖花色的赔率
                   double odds = Iworld.Game.Model.Betting.Odds[color];
                   ///当前限红花色的投注上限
                   double maxBetting = Decimal.ToDouble(Math.Round(new Decimal((currentAll + limit)) / new Decimal(odds), 0));
                   switch (color)
                   {
                       case PokerColor.黑桃:
                           if (currentSumSpade + incrementSpade > maxBetting)
                           {
                               incrementSpade = maxBetting - currentSumSpade;
                              // Logger.Debug("设置黑桃限红:" + incrementSpade);
                           }
                           break;
                       case PokerColor.红桃:
                           if (currentSumHeart + incrementHeart > maxBetting)
                           {
                               incrementHeart = maxBetting - currentSumHeart;
                               //Logger.Debug("设置红桃限红:" + incrementHeart);
                           }
                           break;
                       case PokerColor.梅花:
                           if (currentSumClub + incrementClub > maxBetting)
                           {
                               incrementClub = maxBetting - currentSumClub;
                               //Logger.Debug("设置梅花限红:" + incrementClub);
                           }
                           break;
                       case PokerColor.方块:
                           if (currentSumDiamond + incrementDiamond > maxBetting)
                           {
                               incrementDiamond = maxBetting - currentSumDiamond;
                              // Logger.Debug("设置方块限红:" + incrementDiamond);
                           }
                           break;
                       case PokerColor.王:
                           //所有返还的投注量
                           double returnSumBettings = 0;

                           ///遍历所有的投注,计算需要返还的投注量
                           currentRoundBettingTemps.Values.ToList().ForEach(x =>
                           {
                               if (x.King > 0)
                               {
                                   returnSumBettings += x.Spade;
                                   returnSumBettings += x.Heart;
                                   returnSumBettings += x.Club;
                                   returnSumBettings += x.Diamond;
                               }
                           });
                           ///王的投注量上限:当前的总投注量减去需要返还的投注量在减去限红,最后除以中奖倍数
                           maxBetting = Decimal.ToDouble(Math.Round(new Decimal(((currentAll - returnSumBettings) + limit)) / new Decimal(odds), 0));
                           if (currentSumKing + incrementKing > maxBetting)
                           {
                               incrementKing = maxBetting - currentSumKing;
                               Logger.Debug("设置王限红:" + incrementKing);
                           }
                           break;
                       default:
                           throw new Exception("未知的花色!");
                   }
               }
               #endregion

               exist.Spade += incrementSpade;
               exist.Heart += incrementHeart;
               exist.Club += incrementClub;
               exist.Diamond += incrementDiamond;
               exist.King += incrementKing;

               currentAll += (incrementSpade + incrementHeart + incrementClub + incrementDiamond + incrementKing);
               currentSumSpade += incrementSpade;
               currentSumHeart += incrementHeart;
               currentSumClub += incrementClub;
               currentSumDiamond += incrementDiamond;
               currentSumKing += incrementKing;
               return new TempBetting(exist);
           }
           finally
           {
               tempLock.ReleaseWriterLock();
           }
       }
      
        #endregion

        #region 静态属性
      
        /// <summary>
        /// 最大投注量
        /// </summary>
       static double MaxBetting = 9900;

        /// <summary>
        /// 最小投注量
        /// </summary>
       static double MinBetting = 100;

       #endregion


        /// <summary>
        /// 查询指定用户是否压分
        /// </summary>
        /// <param name="useId"></param>
        /// <returns></returns>
       public static bool IsBetting(int useId)
       {
           try
           {
               tempLock.AcquireReaderLock(timeOut);
               return currentRoundBettingTemps.ContainsKey(useId);
           }
           catch
           {
               return false;
           }
           finally
           {
               tempLock.ReleaseReaderLock();
           }
       }

        /// <summary>
        /// 获取当前的投注信息
        /// </summary>
        /// <returns></returns>
        public static List<TempBetting> GetCurrentBettingsInfo(int? start,int? takeCount)
        {
            try
            {
                tempLock.AcquireReaderLock(timeOut);
                IEnumerable<TempBetting> values = currentRoundBettingTemps.Values;
                List<TempBetting> ret = new List<TempBetting>();
                if (start!= null)
                {
                    values=values.Skip((int)start);
                }
                if (takeCount != null)
                {
                    values=values.Take((int)takeCount);
                }
                values.ToList().ForEach(x => ret.Add(new TempBetting(x) 
                {  
                    Username=x.Username,Golds=x.Golds
                }
                ));
                return ret;
            }
            finally
            {
                tempLock.ReleaseReaderLock();
            }
        }

        #region 主要实例方法

       /// <summary>
       /// 修正投注量
       /// </summary>
       /// <param name="golds"></param>
       void AmendBettingInfo(TempBetting bt)
       {
           //修正上限
           if (bt.Spade > MaxBetting)
           {
               bt.Spade = MaxBetting;
           }
           if (bt.Heart > MaxBetting)
           {
               bt.Heart = MaxBetting;
           }
           if (bt.Club > MaxBetting)
           {
               bt.Club = MaxBetting;
           }
           if (bt.Diamond > MaxBetting)
           {
               bt.Diamond = MaxBetting;
           }
           if (bt.King > MaxBetting)
           {
               bt.King = MaxBetting;
           }
           //修正下限
           if (bt.Spade > 0 && bt.Spade < MinBetting)
           {
               bt.Spade = MinBetting;
           }
           if (bt.Heart > 0 && bt.Heart < MinBetting)
           {
               bt.Heart = MinBetting;
           }
           if (bt.Club > 0 && bt.Club < MinBetting)
           {
               bt.Club = MinBetting;
           }
           if (bt.Diamond > 0 && bt.Diamond < MinBetting)
           {
               bt.Diamond = MinBetting;
           }
           if (bt.King > 0 && bt.King < MinBetting)
           {
               bt.King = MinBetting;
           }
       }

       /// <summary>
       /// 对当前局投注压分,如果同一局重复压分,则会覆盖之前的压分
       /// </summary>
       /// <param name="package"></param>
       /// <returns>返回投注成功的信息量</returns>
       public TempBetting Betting(IPackageForBetting package)
        {
            if (package.Spade <= 0 && package.Heart <= 0 && package.Club <= 0 && package.Diamond <= 0 && package.King <= 0)
            {
                throw new Exception("投注分数有误，压分无效");
            }
            TempBetting bt = new TempBetting(package);
            AmendBettingInfo(bt);
            string runtimeTk = GameManager.GetCurrentTk();
            GameChapter game = db.GameChapters.FirstOrDefault(x => x.Status == GameChapterStatus.正在开奖 
                && x.Tk == runtimeTk && x.NextRoundNumber == bt.GameRoundNum);
            if(game == null)
            {
                throw new Exception("牌局不存在或者期号有误，压分无效");
            }
            GameRound round = db.GameRounds.FirstOrDefault(x=>x.GameChapter.Id == game.Id && x.RoundNum == bt.GameRoundNum);
            if (round == null)
            {
                throw new Exception("牌局不存在，压分无效");
            }
            DateTime now = DateTime.Now;
            int? nextNum = game.NextRoundNumber;
            DateTime? nextTime = game.NextRoundLotteryTime;
            if (nextTime == null || nextNum == null)
            {
                throw new Exception("牌局不存在，压分无效");
            }
            SettingOfGame sg = new SettingOfGame(db);
            if (round.IsLottery || round.IsClosureSingle || ((DateTime)nextTime).AddSeconds(-sg.ClosureSingleTime) <= now)
            {
                throw new Exception("已经封单，压分无效");
            }
            Iworld.Author.Model.Author user = db.Set<Iworld.Author.Model.Author>().Find(bt.UserId);
            if(user == null)
            {
                throw new Exception("用户不存在");
            }
            double sumBetting  = (bt.Spade + bt.Heart+bt.Club+bt.Diamond+bt.King);
            if (user.Golds < sumBetting)
            {
                throw new Exception("余额不足,操作无效");
            }
           ///用户投注之后的余额
            bt.Golds = user.Golds - sumBetting;
            bt.Username = user.Username;
            return CacheOrRefreshBetting(bt, round.PokerColor);
        }

       #endregion
 
        #region 监听方法



       /// <summary>
       /// 监听封单
       /// </summary>
       /// <param name="info"></param>
       [Listen(typeof(GameManager), GameManager.Actions.ClosureSingle, ExecutionOrder.After)]
       public static void ListenClosureSingle(InfoOfSendOnManagerService info)
       {
           GameRound round = (GameRound)info.Args;
           IModelToDbContextOfGame db = (IModelToDbContextOfGame)info.Db;
           BettingManager bm = new BettingManager(db);
           try
           {
               tempLock.AcquireReaderLock(timeOut);
               currentRoundBettingTemps.ToList().ForEach(x => {
                  int uId = x.Key;
                  TempBetting bt = x.Value;
                  Iworld.Author.Model.Author user = db.Set<Iworld.Author.Model.Author>().Find(uId);
                  if (user != null)
                  {
                      Betting betting = new Betting(user, round, bt.Spade, bt.Heart, bt.Club, bt.Diamond, bt.King);
                      bm.OnExecuting(Actions.Create, betting);
                      db.Bettings.Add(betting);
                      bm.OnExecuted(Actions.Create, betting);
                      /*Logger.Debug(string.Format("[" + user.Username + "]投注成功,黑桃{0},红桃{1},梅花{2},方块{3},王{4}",
                          betting.Spade, betting.Heart, betting.Club, betting.Diamond, betting.King));*/
                  }
               });
           }
           finally
           {
               tempLock.ReleaseReaderLock();
           }
       }

       /// <summary>
        /// 监听开奖
        /// </summary>
        /// <param name="info"></param>
        [Listen(typeof(GameManager), GameManager.Actions.Lottery, ExecutionOrder.After)]
        public static void ListenPayLottery(InfoOfSendOnManagerService info)
        {
            GameRound round = (GameRound)info.Args;
            IModelToDbContextOfGame db = (IModelToDbContextOfGame)info.Db;
            db.Bettings.Where(x=>x.Status == BettingStatus.等待开奖).Where(x => x.GameRound.Id == round.Id).ToList().ForEach(x => {
                x.Pay(round);
                BettingManager bm = new BettingManager(db);
                if(x.Bonus > 0)
                {
                    Logger.Debug(x.Owner.Username+"中奖了");
                    bm.OnExecuting(Actions.Win, x);
                    bm.OnExecuted(Actions.Win, x);
                }
                else
                {
                    Logger.Debug(x.Owner.Username + "未中奖");
                    bm.OnExecuting(Actions.Lost, x);
                    bm.OnExecuted(Actions.Lost, x);
                }
            });
            ///开奖之后移除当前局的缓存
            ClearCurrentTemp();
        }

        /// <summary>
        /// 监听中奖
        /// </summary>
        /// <param name="info"></param>
        [Listen(typeof(BettingManager), BettingManager.Actions.Win, ExecutionOrder.After)]
        public static void ListenWin(InfoOfSendOnManagerService info)
        {
            Betting m = (Betting)info.Model;
            IModelToDbContextOfAuthor db = (IModelToDbContextOfAuthor)info.Db;
            Iworld.Author.Manager.GoldChangeRecordManager.CreateGoldChangeRecordArgs args = 
                new Iworld.Author.Manager.GoldChangeRecordManager.CreateGoldChangeRecordArgs()
            {
                Income = m.Bonus,
                UserId = m.Owner.Id,
                Type = "中奖"
            };
            InfoOfCallOnManagerService callInfo =
                new InfoOfCallOnManagerService(db, typeof(GoldChangeRecordManager), GoldChangeRecordManager.Services.CreateGoldChangeRecord, args);
            ManagerService.Call(callInfo);
        }


        /// <summary>
        /// 监听投注
        /// </summary>
        /// <param name="info"></param>
        [Listen(typeof(BettingManager), BettingManager.Actions.Create, ExecutionOrder.After)]
        public static void ListenBetting(InfoOfSendOnManagerService info)
        {
            Betting m = (Betting)info.Model;
            IModelToDbContextOfAuthor db = (IModelToDbContextOfAuthor)info.Db;
            Iworld.Author.Manager.GoldChangeRecordManager.CreateGoldChangeRecordArgs args =
                new Iworld.Author.Manager.GoldChangeRecordManager.CreateGoldChangeRecordArgs()
                {
                    Expenses = (m.Spade + m.Heart + m.Club + m.Diamond + m.King),
                    UserId = m.Owner.Id,
                    Type = "压分"
                };
            InfoOfCallOnManagerService callInfo =
                new InfoOfCallOnManagerService(db, typeof(GoldChangeRecordManager), GoldChangeRecordManager.Services.CreateGoldChangeRecord, args);
            ManagerService.Call(callInfo);
        }

        /// <summary>
        /// 监听赠送金币
        /// </summary>
        /// <param name="info"></param>
      [Listen(typeof(GiveGoldsRecordManager), GiveGoldsRecordManager.Actions.Create, ExecutionOrder.After)]
        public static void ListenGiveGolds(InfoOfSendOnManagerService info)
        {
          GiveGoldsRecord model = (GiveGoldsRecord)info.Model;
            try
            {
                tempLock.AcquireReaderLock(timeOut);
                if(currentRoundBettingTemps.ContainsKey(model.From.Id))
                {
                    throw new Exception("压分状态下不能赠送金币.");
                }
            }
            finally
            {
                tempLock.ReleaseReaderLock();
            }
        }



      /// <summary>
      /// 移除用户的时候清除相关信息
      /// </summary>
      /// <param name="info"></param>
      [Listen(typeof(AuthorManager), AuthorManager.Actions.Remove, ExecutionOrder.Before)]
      public static void Monitor_RemoveRecordsWhenUserRemoving(InfoOfSendOnManagerService info)
      {
          IModelToDbContextOfGame db = (IModelToDbContextOfGame)info.Db;
          Iworld.Author.Model.Author model = (Iworld.Author.Model.Author)info.Model;
          db.Bettings.Where(x => x.Owner.Id == model.Id).ToList()
              .ForEach(x =>
              {
                  db.Bettings.Remove(x);
              });
      }

       #endregion

        #region 内部结构


        /// <summary>
        /// 投注信息缓存
        /// </summary>
        public class TempBetting : IPackageForBetting
        {

            public TempBetting(int rId,int uId)
            {
                this.GameRoundNum = rId;
                this.UserId = uId;
                this.Spade =0;
                this.Heart =0;
                this.Club = 0;
                this.Diamond = 0;
                this.King =0;
            }

            public TempBetting(IPackageForBetting package)
            {
                this.GameRoundNum = package.GameRoundNum;
                this.UserId = package.UserId;
                this.Spade = package.Spade;
                this.Heart = package.Heart;
                this.Club = package.Club;
                this.Diamond = package.Diamond;
                this.King = package.King;
            }

            /// <summary>
            /// 投注牌局号
            /// </summary>
            public int GameRoundNum { set; get; }

            /// <summary>
            /// 投注人的ID
            /// </summary>
            public int UserId { set; get; }

            /// <summary>
            /// 黑桃的投注量
            /// </summary>
            public double Spade { set; get; }

            /// <summary>
            /// 红桃的投注量
            /// </summary>
            public double Heart { set; get; }

            /// <summary>
            /// 梅花的投注量
            /// </summary>
            public double Club { set; get; }

            /// <summary>
            /// 方块的投注量
            /// </summary>
            public double Diamond { set; get; }

            /// <summary>
            /// 王的投注量
            /// </summary>
            public double King { set; get; }

            /// <summary>
            /// 用户剩余金币
            /// </summary>
            public double Golds { set; get; }

            /// <summary>
            /// 用户名称
            /// </summary>
            public string Username { set; get; }
        }

        /// <summary>
        /// 投注的数据集
        /// </summary>
        public interface IPackageForBetting
        {
            /// <summary>
            /// 投注的牌局号
            /// </summary>
            int GameRoundNum { set; get; }

            /// <summary>
            /// 投注人的ID
            /// </summary>
            int UserId { set; get; }

            /// <summary>
            /// 黑桃的投注量
            /// </summary>
            double Spade { set; get; }

            /// <summary>
            /// 红桃的投注量
            /// </summary>
            double Heart { set; get; }

            /// <summary>
            /// 梅花的投注量
            /// </summary>
            double Club { set; get; }

            /// <summary>
            /// 方块的投注量
            /// </summary>
            double Diamond { set; get; }

            /// <summary>
            /// 王的投注量
            /// </summary>
            double King { set; get; }


        }

        public enum Actions
        {
            /// <summary>
            /// 创建投注
            /// </summary>
            Create,
            Update,
            Remove,
            /// <summary>
            /// 中奖
            /// </summary>
            Win,
            /// <summary>
            /// 未中奖
            /// </summary>
            Lost

        }

        #endregion
    }
 
}
